Check Frame function calls at publish validation - #32282
Open
davidebbo wants to merge 1 commit into
Open
Conversation
`dsbx frame validate` now reports UI code that calls a function the Frame's manifest does not declare, or that passes an input the function's `schema.input` rejects. Both otherwise fail only once a viewer triggers the call, and nothing else type-checks a v2 Frame's UI source: esbuild strips types without checking them. Two passes. The reference pass is syntactic, so it can name what went wrong: a literal that matches no declared function, or a leftover `<podId>/<slug>` Pod reference, which `resolveFrameFunctionReference` rejects outright for v2. Computed references are skipped. The input pass builds a virtual TypeScript program over the Frame's sources with a generated `@dust/react-hooks` declaration whose hooks map a function name to the input type compiled from its published JSON Schema, and keeps only the diagnostics anchored inside a call's input argument. Warnings, never errors, and the input pass is wrapped: a schema the converter cannot express must not stand between an author and a publish.
davidebbo
force-pushed
the
frame-function-validator
branch
from
September 11, 2026 06:43
d49864f to
789f402
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes https://github.com/dust-tt/tasks/issues/10366
The Frames v2 equivalent of the publish-time check that
validate_frame_pod_functions.tsused to do for Pod functions, rebuilt against the thing Frames actually call: their own manifest functions.dsbx frame validatenow reports UI code that calls a function the manifest does not declare, or that passes an input the function'sschema.inputrejects. Both otherwise surface only once a viewer triggers the call — and nothing else type-checks a v2 Frame's UI source, since esbuild strips types without checking them. This is the first thing that reads that source with a compiler.Two passes, deliberately different
References are checked syntactically, not through the type system, so the message can say what is actually wrong.
resolveFrameFunctionReferenceaccepts exactly one form for v2 — a bare manifest name — so the valid set is known exactly, and a bad reference gets either the list of declared functions or, for a leftover<podId>/<slug>Pod reference, the bare name to use instead. Computed references are skipped rather than guessed at. This pass also runs when the manifest declares no functions at all, which the old validator could not do: an empty contract map makeskeyofresolve toneverand rejects every call with a misleading "not found".Inputs are checked by a virtual TypeScript program over the Frame's sources, with a generated
@dust/react-hooksdeclaration mapping each declared function name to the input type compiled from its published JSON Schema. Only diagnostics anchored inside a call's input argument are kept, which is what keeps the Frame's own unchecked type errors out of the report — there is a test for that.The declaration types the input through a conditional on the inferred name rather than constraining the name to
keyof, and that detail is load-bearing in both directions. A name outside the map resolves the input toany, so an unknown or computed reference falls through untyped instead of cascading onto the input that the reference pass already reported. And unlike the overload pair I first wrote, there is no permissive signature for a mistyped input to fall back to — TypeScript picks the first applicable overload, so a wrong input simply made the strict overload inapplicable and the call passed silently. That version reported nothing and looked like it worked.Both hooks are covered, including
usePodFunctionMutation, whose input arrives at thetriggerit returns rather than at the hook call: the generic flows into the returned type, sotrigger({ title: 42 })is checked at the trigger call site. Aliased and namespace imports are followed.Advisory by construction
Contracts are authored in Zod and checked here through their extracted JSON Schema, so runtime-only refinements are not always expressible as TypeScript types — an input can pass here and still fail the authoritative Zod validation, and heuristics run in the other direction too. Everything is a
ValidationWarning, the input pass is wrapped so a schema the converter cannot express or a compiler failure cannot block a publish, and there is a code contract (frame-function-check-stays-advisory) saying so, because the tempting next step is to promote a finding to aFramePublicationError.ValidationWarning.typegains aframe_functionmember. Nothing switches on the field — it is only rendered — andframe_function: …reads better thantypescript: …for a missing-function reference.Not in scope
Output shapes.
outputSchemais extracted at publish time and typingdatawould catch a UI reading a field its function never returns, but those diagnostics land outside the call arguments, where nothing separates them from the Frame's own uncompiled type errors. Worth doing behind a narrower filter; not worth the false-positive budget here.Testing
15 unit tests on the pass itself (pure, no DB): each warning kind, both hooks, aliased and namespace imports, computed references, a call in an imported component, the empty-manifest case, the cap, and a Frame whose unrelated type error must not be reported.
front: 31 files / 226 tests pass across frames, viz, skills and interactive-content.front-apiframes routes: 6 files / 25 tests. Typecheck clean infrontandfront-api; biome clean.One finding worth noting from writing the tests: a misspelled property produces TS2561 ("Did you mean to write 'title'?"), not the 2353/2345 the old validator listed, so the typo case — the most likely mistake of all — would have gone unreported on the inherited code list.